package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointOnCircumference;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.anchorColor;
import static xj.graph2d.DrawShapeConstants.ctrStroke;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.defaultFrameColor;
import static xj.graph2d.DrawShapeConstants.frameMarkFilled;
import static xj.graph2d.DrawShapeConstants.markStroke;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.TwoEndsNodeShape;
import xj.graph2d.graph.Geometry;
import xj.graph2d.util.DrawingUtilities;
import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;

public class CircleShape extends TwoEndsNodeShape {

  public CircleShape() {
    this(false);
  }

  public CircleShape(boolean createComp) {
    super(createComp);
    allowRotation = false;
    c1 = new Point2DFloat();
    c2 = new Point2DFloat();
  }

  public CircleShape(float x, float y, float radius) {
    this(x, y, radius, false, false);
  }

  public CircleShape(float x, float y, float radius, boolean filled) {
    this(x, y, radius, filled, false);
  }

  public CircleShape(float x, float y, float radius, boolean filled,
      boolean createComp) {
    super(createComp);
    this.radius = radius;
    this.filled = filled;
    allowRotation = false;
    c1 = new Point2DFloat();
    c2 = new Point2DFloat();
    setCircle(x, y, radius);
  }

  public CircleShape(float x1, float y1, float x2, float y2) {
    this(x1, y1, x2, y2, false, false);
  }

  public CircleShape(float x1, float y1, float x2, float y2, boolean filled) {
    this(x1, y1, x2, y2, filled, false);
  }

  public CircleShape(float x1, float y1, float x2, float y2, boolean filled,
		     boolean createComp) {
    super(createComp);
    allowRotation = false;
    this.filled = filled;
    c1 = new Point2DFloat();
    c2 = new Point2DFloat();
    setCircle(x1, y1, x2, y2);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof CircleShape) {
      CircleShape c = (CircleShape) s;
      float cx = (float) c1.getX();
      float cy = (float) c1.getY();
      float px = (float) c2.getX();
      float py = (float) c2.getY();
      c.setCircle(cx, cy, px, py);
    }
  }

  public DrawShape makeInstance() {
    return new CircleShape(comp != null);
  }

  @Override
  public String getName() {
    return "Circle";
  }

  public float getX() {
    return (float) c1.getX();
  }

  public float getY() {
    return (float) c1.getY();
  }

  public float getRadius() {
    return radius;
  }

  /**
   * setCircle - set the parameters of the circle
   * 
   * @param x
   *                Point (x, y) is the center of the circle
   * @param y
   *                Point (x, y) is the center of the circle
   * @param radius
   *                The radius of the circle
   * @return none
   */
  public void setCircle(float x, float y, float radius) {
    setCircle(x, y, x + radius, y);
  }

  /**
   * setCircle - set the parameters of the circle
   * 
   * @param cx
   *                Point (cx, cy) is the center of the circle
   * @param cy
   *                Point (cx, cy) is the center of the circle
   * @param px
   *                Point (px, py) is on the circumference of the circle
   * @param py
   *                Point (px, py) is on the circumference of the circle
   * @return none
   */
  public void setCircle(float cx, float cy, float px, float py) {
    float dx = px - cx;
    float dy = py - cy;
    radius = (float) Math.sqrt(dx * dx + dy * dy);
    c1.setLocation(cx, cy);
    c2.setLocation(px, py);
    p1.setLocation(cx - radius, cy - radius);
    p2.setLocation(cx + radius, cy + radius);
    calculateBounds();
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    float cx = (x1 + x2) / 2;
    float cy = (y1 + y2) / 2;
    float r = Math.abs(x2 - x1) / 2;
    setCircle(cx, cy, cx + r, cy);
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    scaleSubshapes(x1, y1, x2, y2);
    float cx = (x1 + x2) / 2;
    float cy = (y1 + y2) / 2;
    float r = Math.abs(x2 - x1) / 2;
    setCircle(cx, cy, cx + r, cy);
  }

  @Override
  public void move(float dx, float dy) {
    c1.setLocation(c1.getX() + dx, c1.getY() + dy);
    c2.setLocation(c2.getX() + dx, c2.getY() + dy);
    super.move(dx, dy);
  }
  
  @Override
  public void normalize() {
  }

  @Override
  public boolean isEdgeSensitive() {
    return false;
  }

  @Override
  public boolean isCornorSensitive() {
    return false;
  }

  @Override
  public void moveCircumference(float px, float py) {
    float cx = (float) c1.getX();
    float cy = (float) c1.getY();
    setCircle(cx, cy, px, py);
  }

  @Override
  public void draw(Graphics2D g2) {
    float cx = (float) c1.getX();
    float cy = (float) c1.getY();
    Ellipse2D shape = new Ellipse2D.Float(cx - radius, cy - radius, 
					  2 * radius, 2 * radius);
    if (filled) {
      fillBackground(g2, cx - radius, cy - radius, 
		     2 * radius, 2 * radius, shape);
      if (showborder) {
	g2.draw(shape);
      }
    } else {
      g2.draw(shape);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    DrawAttr attr = getDrawAttr();
    int penSize = 1;
    if (attr != null) {
      g2.setColor(attr.getPenColor());
      penSize = attr.getPenSize();
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    int marksize = penSize + 2 * (int) d;
    float md = marksize / 2.0f;
    float dx = x2 - x1;
    float dy = y2 - y1;
    float radius = (float) Math.sqrt(dx * dx + dy * dy);
    g2.draw(new Ellipse2D.Float(x1 - radius, y1 - radius, 2 * radius,
	2 * radius));
    Color saveColor = g2.getColor();
    g2.setColor(anchorColor);
    g2.fill(new Rectangle2D.Float(x1 - md, y1 - md, marksize, marksize));
    Stroke s = g2.getStroke();
    g2.setStroke(ctrStroke);
    g2.draw(new Line2D.Float(x1, y1, x2, y2));
    g2.setStroke(s);
    g2.setColor(saveColor);
  }

  @Override
  public Shape getOutlineShape() {
    float cx = (float) c1.getX();
    float cy = (float) c1.getY();
    return new Ellipse2D.Float(cx - radius, cy - radius, 2 * radius, 2 * radius);
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.getPenSize();
    }
    int marksize = DrawingUtilities.getMarkSize(penSize);
    float md = marksize / 2.0f;
    float cx = (float) c1.getX();
    float cy = (float) c1.getY();
    g2.setStroke(markStroke);
    DrawingUtilities.drawRectFrame(g2, p1, p2, 
				   needFrameRect, false, false,
				   frameMarkFilled,
				   marksize, defaultFrameColor, anchorColor);
    Color saveColor = g2.getColor();
    g2.setColor(anchorColor);
    Rectangle2D r = new Rectangle2D.Float(cx - md, cy - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(r);
    } else { 
      g2.draw(r);
    }

    Stroke s = g2.getStroke();
    g2.setStroke(ctrStroke);
    g2.draw(new Line2D.Float(cx, cy, (float) c2.getX(), (float) c2.getY()));
    g2.setStroke(s);
    g2.setColor(saveColor);
  }

  @Override
  public int isInside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  @Override
  public int isInside(float px, float py) {
    int result = PointOutside;
    float cx = (float) c1.getX();
    float cy = (float) c1.getY();
    float dx = px - cx;
    float dy = py - cy;
    float dis = (float) Math.sqrt(dx * dx + dy * dy);
    float r = radius;
    if (r < (1.5f * d)) {
      r += d;
    }
    if (dis <= r + d) {
      if (dis >= r - d) {
	result = PointOnCircumference;
      } else {
	result = PointInside;
      }
    }
    return result;
  }

  /* return the intersection point of p and the center of the node */
  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D c = getCenter();
      c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
      if (isInside(p) == PointOutside) {
	// find the intersection closer to p
	return Geometry.intersectionEllipseLine(getULCorner(), getLRCorner(),
	    p, c);
      } else {
	// find the intersection closer to c
	return Geometry.intersectionEllipseLine(getULCorner(), getLRCorner(),
	    c, p);
      }
    } else {
      return null;
    }
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("r", radius);
    return attrs;
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String nodename;
      if (c1 != null) {
	nodename = getPrefixedName("Center", namespacePrefix);
	out.println("<" + nodename + ' '
	    + getXMLAttributeString("x", c1.getX()) + ' '
	    + getXMLAttributeString("y", c1.getY()) + "/>");
      }
      if (c2 != null) {
	nodename = getPrefixedName("Circumference", namespacePrefix);
	out.println("<" + nodename + ' '
	    + getXMLAttributeString("x", c2.getX()) + ' '
	    + getXMLAttributeString("y", c2.getY()) + "/>");
      }
    }
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      try {
	String rs = node.getAttribute("r");
	radius = Float.parseFloat(rs);
      } catch (NumberFormatException ex) {
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String name = e.getLocalName();
      String xs, ys;
      float x, y;
      if ("Center".equals(name)) {
	xs = e.getAttribute("x");
	ys = e.getAttribute("y");
	try {
	  x = Float.parseFloat(xs);
	  y = Float.parseFloat(ys);
	  c1.setLocation(x, y);
	} catch (NumberFormatException ex) {
	}
      } else if ("Circumference".equals(name)) {
	xs = e.getAttribute("x");
	ys = e.getAttribute("y");
	try {
	  x = Float.parseFloat(xs);
	  y = Float.parseFloat(ys);
	  c2.setLocation(x, y);
	} catch (NumberFormatException ex) {
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  protected float radius;

  protected Point2D c1, c2;

}
